package com.peas.platform.common.core.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.peas.platform.common.core.configuration.RequestContextUtil;
import com.peas.platform.common.core.constant.Constants;
import com.peas.platform.common.core.dto.ResponseDto;
import com.peas.platform.common.core.exception.BaseException;
import feign.FeignException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Optional;
import java.util.Set;

/**
 * @author caizhh
 */
@Slf4j
public class ExceptionUtil {

    public static ResponseDto<String> render(Throwable throwable) {
        return render(HttpStatus.INTERNAL_SERVER_ERROR, throwable);
    }

    public static ResponseDto<String> render(HttpStatus httpStatus, Throwable throwable) {
        Throwable exception = getRealException(throwable);
        String code;
        String message;
        switch (exception.getClass().getName()) {
            case Constants.BASE_PACKAGE + ".common.core.exception.BaseException": {
                BaseException base = (BaseException) exception;
                code = base.getCode();
                message = base.getMessage();
                break;
            }
            case "org.springframework.validation.BindException": {
                BindException e = (BindException) exception;
                code = "997";
                message = "参数错误(" + e.getClass().getSimpleName() + ")。";
                List<ObjectError> list = e.getAllErrors();
                if (CollUtil.isNotEmpty(list)) {
                    for (ObjectError oe : list) {
                        message = oe.getDefaultMessage();
                        if (StrUtil.isNotBlank(message)) {
                            break;
                        }
                    }
                }
                break;
            }
            case "javax.validation.ConstraintDeclarationException":
            case "javax.validation.ConstraintDefinitionException":
            case "javax.validation.NoProviderFoundException":
            case "javax.validation.ConstraintViolationException":
            case "javax.validation.ValidationException": {
                ConstraintViolationException e = (ConstraintViolationException) exception;
                code = "996";
                message = "参数校验不通过(" + e.getClass().getSimpleName() + ")。";
                Set<ConstraintViolation<?>> set = e.getConstraintViolations();
                ConstraintViolation<?> c = CollUtil.getFirst(set);
                if (StrUtil.isNotBlank(c.getMessage())) {
                    message = message + "[" + c.getMessage() + "]";
                }
                break;
            }
            case "java.net.UnknownServiceException": {
                code = "995";
                message = buildMessage("未知服务", null, exception);
                break;
            }
            case "java.net.UnknownHostException": {
                code = "994";
                message = buildMessage("未知主机", null, exception);
                break;
            }
            case "java.net.SocketTimeoutException": {
                code = "993";
                message = buildMessage("Socket连接超时", null, exception);
                break;
            }
            case "java.net.SocketException": {
                code = "992";
                message = buildMessage("Socket异常", null, exception);
                break;
            }
            case "java.net.ConnectException": {
                code = "991";
                message = buildMessage("连接异常", null, exception);
                break;
            }
            case "java.lang.IllegalArgumentException": {
                code = "990";
                message = buildMessage("操作失败", null, exception);
                break;
            }
            case "java.lang.NoSuchMethodError" :
            case "java.lang.NoSuchMethodException": {
                code = "989";
                message = buildMessage("方法不支持", null, exception);
                break;
            }
            case "java.lang.NoSuchFieldError":
            case "java.lang.NoSuchFieldException": {
                code = "988";
                message = buildMessage("属性不支持", null, exception);
                break;
            }
            case "java.lang.ClassNotFoundException": {
                code = "987";
                message = buildMessage("找不到类", null, exception);
                break;
            }
            case "java.lang.IllegalAccessError":
            case "java.lang.IllegalAccessException": {
                code = "986";
                message = buildMessage("非法连接", null, exception);
                break;
            }
            case "java.lang.reflect.UndeclaredThrowableException": {
                code = "985";
                message = buildMessage("未知异常", null, exception);
                break;
            }
            case "java.lang.NullPointerException": {
                code = "984";
                message = buildMessage("空指针异常", null, exception);
                break;
            }
            case "java.lang.ClassCastException": {
                code = "983";
                message = buildMessage("类转换异常", null, exception);
                break;
            }
            case "java.lang.IndexOutOfBoundsException": {
                code = "982";
                message = buildMessage("数组越界", null, exception);
                break;
            }
            case "java.lang.NumberFormatException": {
                code = "981";
                message = buildMessage("数字转换错误", null, exception);
                break;
            }
            case "java.lang.IllegalStateException": {
                code = "980";
                message = buildMessage("状态校验", null, exception);
                break;
            }
            case "java.lang.ArrayIndexOutOfBoundsException": {
                code = "979";
                message = buildMessage("数组越界", null, exception);
                break;
            }
            case "java.lang.OutOfMemoryError": {
                code = "978";
                message = buildMessage("内存溢出", null, exception);
                break;
            }
            case "java.lang.StackOverflowError": {
                code = "977";
                message = buildMessage("栈溢出", null, exception);
                break;
            }
            case "java.sql.BatchUpdateException":
            case "java.sql.SQLClientInfoException":
            case "java.sql.SQLDataException":
            case "java.sql.SQLTimeoutException":
            case "java.sql.SQLException":
            case "org.springframework.jdbc.CannotGetJdbcConnectionException":
            case "org.springframework.jdbc.InvalidResultSetAccessException":
            case "org.springframework.jdbc.IncorrectResultSetColumnCountException":
            case "org.springframework.jdbc.JdbcUpdateAffectedIncorrectNumberOfRowsException":
            case "org.springframework.jdbc.BadSqlGrammarException": {
                code = "976";
                message = buildMessage("数据库操作异常", null, exception);
                break;
            }
            case "org.springframework.dao.DataIntegrityViolationException": {
                code = "975";
                message = buildMessage("数据完整性异常", null, exception);
                break;
            }
            case "org.springframework.http.converter.HttpMessageNotReadableException":
            case "org.springframework.http.converter.HttpMessageNotWritableException":
            case "org.springframework.http.converter.HttpMessageConversionException": {
                code = "974";
                message = buildMessage("数据错误", null, exception);
                break;
            }
            case "org.springframework.web.HttpRequestMethodNotSupportedException": {
                code = "973";
                message = buildMessage("请求的Method不支持", null, exception);
                break;
            }
            case "org.springframework.web.bind.MethodArgumentNotValidException": {
                code = "972";
                MethodArgumentNotValidException e = (MethodArgumentNotValidException) exception;
                String msg = null;
                if (CollUtil.isNotEmpty(e.getAllErrors())) {
                    msg = e.getAllErrors().get(0).getDefaultMessage();
                }
                message = buildMessage("参数不正确", msg, exception);
                break;
            }
            case "java.util.concurrent.ExecutionException": {
                code = "971";
                message = buildMessage("执行错误", null, exception);
                break;
            }
            case "com.baomidou.mybatisplus.core.exceptions.MybatisPlusException": {
                code = "970";
                message = buildMessage("数据异常", null, exception);
                break;
            }
            case "cn.hutool.json.JSONException": {
                code = "969";
                message = buildMessage("异常", null, exception);
                break;
            }
            case "org.apache.ibatis.exceptions.TooManyResultsException": {
                code = "968";
                message = buildMessage("数据异常", "[getOne]找到多条数据", exception);
                break;
            }
            case "java.util.ConcurrentModificationException": {
                code = "967";
                message = buildMessage("并发修改异常", exception.getMessage(), exception);
                break;
            }
            case "org.springframework.data.redis.TooManyClusterRedirectionsException": {
                code = "967";
                message = buildMessage("Redis连接错误", exception.getMessage(), exception);
                break;
            }
            case "org.springframework.data.redis.RedisSystemException": {
                code = "966";
                message = buildMessage("Redis系统异常", exception.getMessage(), exception);
                break;
            }
            case "org.mybatis.spring.MyBatisSystemException": {
                code = "965";
                message = buildMessage("MyBatis系统异常", exception.getMessage(), exception);
                break;
            }
            case "org.springframework.web.bind.MissingServletRequestParameterException:": {
                code = "964";
                message = buildMessage("请求参数不正确", null, exception);
                break;
            }
            case "java.lang.ArithmeticException": {
                code = "963";
                message = buildMessage("计算错误", null, exception);
                break;
            }
            case "java.lang.UnsupportedOperationException": {
                code = "963";
                message = buildMessage("不支持操作", null, exception);
                break;
            }
            case "feign.FeignException": {
                code = "899";
                message = buildMessage("Feign调用异常", exception.getMessage(), exception);
                break;
            }
            case "feign.FeignException$BadRequest":
            case "feign.FeignException$Unauthorized":
            case "feign.FeignException$Forbidden":
            case "feign.FeignException$NotFound":
            case "feign.FeignException$MethodNotAllowed":
            case "feign.FeignException$NotAcceptable":
            case "feign.FeignException$Conflict":
            case "feign.FeignException$Gone":
            case "feign.FeignException$UnsupportedMediaType":
            case "feign.FeignException$TooManyRequests":
            case "feign.FeignException$UnprocessableEntity":
            case "feign.FeignException$FeignClientException": {
                code = "887";
                String str = "Feign调用客户端异常:";
                message = str + exception.getClass().getName();
                FeignException.FeignClientException err = (FeignException.FeignClientException) exception;
                Optional<ByteBuffer> op = err.responseBody();
                if (op != null && op.isPresent()) {
                    message = str + buildFeignMessage(op.get(), message);
                }
                break;
            }
            case "feign.FeignException$InternalServerError":
            case "feign.FeignException$NotImplemented":
            case "feign.FeignException$BadGateway":
            case "feign.FeignException$ServiceUnavailable":
            case "feign.FeignException$GatewayTimeout":
            case "feign.FeignException$FeignServerException": {
                code = "881";
                String str = "Feign调用服务端异常:";
                message = str + exception.getClass().getName();
                FeignException.FeignServerException err = (FeignException.FeignServerException) exception;
                Optional<ByteBuffer> op = err.responseBody();
                if (op != null && op.isPresent()) {
                    message = str + buildFeignMessage(op.get(), message);
                }
                break;
            }
            default: {
                code = "999";
                message = buildMessage(Constants.DEFAULT_ERROR_MESSAGE, null, exception);
            }
        }
        return render(code, message, httpStatus, throwable);
    }

    private static ResponseDto<String> render(String code, String message, HttpStatus httpStatus, Throwable throwable) {
        long curr = System.currentTimeMillis();
        Long start = RequestContextUtil.getInitTimestampMillis();
        long duration = start != null ? curr - start : 0;
        ResponseDto<String> result = new ResponseDto<>(code, message, null, duration, null, throwable);
        log.error("httpStatus:{}", httpStatus.getReasonPhrase());
        log.error("ExceptionCode:{},ExceptionMessage:{}", code, message);
        result.setDuration(duration);
        result.setRequestId(RequestContextUtil.getCurrentRequestId());
        return result;
    }

    private static Throwable getRealException(Throwable throwable) {
        log.error("Exception:", throwable);
        while (throwable.getClass().equals(InvocationTargetException.class)) {
            throwable = ((InvocationTargetException) throwable).getTargetException();
        }
        while (throwable.getCause() != null) {
            throwable = throwable.getCause();
        }
        return throwable;
    }

    private static String buildFeignMessage(ByteBuffer bb, String defaultMsg) {
        String body = new String(bb.array());
        log.debug(body);
        try {
            JSONObject json = JSON.parseObject(body);
            if (StrUtil.isNotBlank(json.getString("code")) && StrUtil.isNotBlank(json.getString("message"))) {
                // code = json.getString("code");
                return json.getString("message");
            }
        } catch (Exception e) {
            if (StrUtil.isNotBlank(body)) {
                return body.replaceAll("\n", "").replaceAll("\r\n", "");
            }
        }
        return defaultMsg;
    }

    private static String buildMessage(String prefix, String msg, Throwable e) {
        StringBuilder message = new StringBuilder(prefix);
        if (e == null) {
            log.error("exception is null");
            msg = "null";
            return message.append(":(").append(msg).append(")").append("。").toString();
        }
        log.error(e.getMessage(), e);
        if (StrUtil.isBlank(e.getMessage())) {
            msg = e.getClass().getSimpleName();
        }
        if (StrUtil.isNotBlank(msg)) {
            message.append(":(").append(msg).append(")").append("。");
        } else if (StrUtil.isNotBlank(e.getMessage())) {
            message.append(":(").append(e.getMessage()).append(")").append("。");
        }
        return message.toString();
    }
}
